package edu.uoc.vmerinom.tfc.support;

import java.io.Serializable;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Random;
import java.util.ResourceBundle;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.faces.model.SelectItem;

import org.jboss.seam.annotations.Logger;
import org.jboss.seam.log.Log;

/**
 * Clase de soporte con métodos de utilidades de conversión de formatos y
 * validaciones genéricas.
 * 
 * @author Víctor Manuel Merino Martínez
 * 
 */
public class Utils implements Serializable {
	/**
	 * Generated Serial Version UID.
	 */
	private static final long serialVersionUID = -1884490140009468934L;

	/**
	 * Algoritmo de cifrado MD2.
	 */
	public static String MD2 = "MD2";
	/**
	 * Algoritmo de cifrado MD5.
	 */
	public static String MD5 = "MD5";
	/**
	 * Algoritmo de cifrado SHA-1.
	 */
	public static String SHA1 = "SHA-1";
	/**
	 * Algoritmo de cifrado SHA-256.
	 */
	public static String SHA256 = "SHA-256";
	/**
	 * Algoritmo de cifrado SHA-384.
	 */
	public static String SHA384 = "SHA-384";
	/**
	 * Algoritmo de cifrado SHA-512.
	 */
	public static String SHA512 = "SHA-512";
	/**
	 * Constante que representa el formato del día.
	 */
	public static final String DAY = "dd";
	/**
	 * Constante que representa el formato del mes.
	 */
	public static final String MONTH = "MM";
	/**
	 * Constante que representa el formato del año.
	 */
	public static final String YEAR = "yyyy";

	/**
	 * Log para la trazabilidad de errores.
	 */
	@Logger
	private static Log log;

	/**
	 * Encripta una cadena de texto usando el algoritmo cuyo identificador
	 * recibe por parámetros.
	 * 
	 * @param text
	 *            Cadena de texto a encriptar
	 * @param algorithm
	 *            Identificador del algoritmo de encriptación. Puede ser: MD2,
	 *            MD5, SHA-1, SHA-256, SHA-384, SHA-512
	 * @return Cadena de texto encriptada
	 */
	public static String encrypt(final String text, final String algorithm) {
		byte[] digest = null;
		final byte[] buffer = text.getBytes();
		try {
			final MessageDigest message = MessageDigest.getInstance(algorithm);
			message.reset();
			message.update(buffer);
			digest = message.digest();
		} catch (final NoSuchAlgorithmException ex) {
			log.error("Error creando Digest", ex);
		}
		final StringBuffer hash = new StringBuffer();
		for (final byte aux : digest) {
			final int b = aux & 0xff;
			if (Integer.toHexString(b).length() == 1) {
				hash.append("0");
			}
			hash.append(Integer.toHexString(b));
		}
		return hash.toString();
	}

	/**
	 * Transforma la fecha que recibe por parámetros en una cadena según el
	 * formato que recibe.
	 * 
	 * @param date
	 *            Fecha
	 * @param format
	 *            Formato
	 * @return Cadena con la fecha según el formato indicado
	 */
	public static String date2String(final Date date, final String format) {
		String fecha = "";
		try {
			final SimpleDateFormat sdf = new SimpleDateFormat(format);
			fecha = sdf.format(date);
		} catch (final Exception e) {
			fecha = "";
		}
		return fecha;
	}

	/**
	 * Devuelve un objeto tipo <tt>String</tt> con los apellidos. Los datos se
	 * obtienen de las cadenas que recibe por parámetros. Si una cadena es
	 * <tt>null</tt> o es vacía no se usará. Si recibe los parámetros [SANTOS] y
	 * [TRUJILLO] el método devolverá la cadena "SANTOS TRUJILLO". Si el método
	 * recibe los parámetros [<tt>null</tt>] y [TRUJILLO] devolverá la cadena
	 * "TRUJILLO".
	 * 
	 * @param apellido1
	 *            Cadena que contiene el primer apellido
	 * @param apellido2
	 *            Cadena que contiene el segundo apellido
	 * @return Cadena con los apellidos
	 */
	public static String getApellidos(final String apellido1, final String apellido2) {
		final String vacia = "";
		final String espacio = " ";
		final StringBuffer apellidos = new StringBuffer(vacia);

		if ((apellido1 != null) && !vacia.equals(apellido1.trim())) {
			apellidos.append(apellido1.trim());
		}

		if ((apellido2 != null) && !vacia.equals(apellido2.trim())) {
			if (!vacia.equals(apellidos.toString())) {
				apellidos.append(espacio);
				apellidos.append(apellido2.trim());
			} else {
				apellidos.append(apellido2.trim());
			}
		}

		return apellidos.toString();
	}

	/**
	 * Devuelve un objeto tipo <tt>String</tt> con el nombre y los apellidos.
	 * Los datos se obtienen de las cadenas que recibe por parámetros. Si una
	 * cadena es <tt>null</tt> o es vacía no se usará. Si recibe los parámetros
	 * [ANTONIO], [SANTOS], [TRUJILLO] el método devolverá la cadena
	 * "ANTONIO SANTOS TRUJILLO". Si el método recibe los parámetros [
	 * <tt>null</tt>], [SANTOS] y [] devolverá la cadena "SANTOS".
	 * 
	 * @param nombre
	 *            Cadena que contiene el nombre
	 * @param apellido1
	 *            Cadena que contiene el primer apellido
	 * @param apellido2
	 *            Cadena que contiene el segundo apellido
	 * @return Cadena con el nombre y los apellidos
	 */
	public static String getNombreApellidos(final String nombre, final String apellido1, final String apellido2) {
		final String vacia = "";
		final String espacio = " ";
		final StringBuffer nombreApellidos = new StringBuffer(vacia);

		if ((nombre != null) && !vacia.equals(nombre.trim())) {
			nombreApellidos.append(nombre.trim());
			final String apellidos = getApellidos(apellido1, apellido2);
			if (!vacia.equals(apellidos)) {
				nombreApellidos.append(espacio);
				nombreApellidos.append(apellidos);
			}
		}

		return nombreApellidos.toString();
	}

	/**
	 * Genera una cadena aleatoria de la longitud que recibe por parámetros. La
	 * cadena contendrá números, carácteres en mayúsculas y minúsculas
	 * dependiendo de los valores que recibe por parámetros. Si todos los
	 * parámetros lógicos tienen valor <code>false</code>, la cadena contendrá
	 * valores numéricos y carácteres en mayúsculas y minúsculas.
	 * 
	 * @param length
	 *            Longitud de la cadena a generar
	 * @param numbers
	 *            Lógico que indica si se desea que la cadena aleatoria a
	 *            generar contenga números o no.
	 * @param upperCase
	 *            Lógico que indica si se desea que la cadena aleatoria a
	 *            generar contenga carácteres en mayúsculas o no.
	 * @param lowerCase
	 *            Lógico que indica si se desea que la cadena aleatoria a
	 *            generar contenga carácteres en minúsculas o no.
	 * @return Cadena aleatoria generada
	 */
	public static String generateRandomString(final int length, final boolean numbers, final boolean upperCase, final boolean lowerCase) {
		final StringBuffer randomString = new StringBuffer();
		final long millis = System.currentTimeMillis();
		final Random random = new Random(millis);
		int i = 0;
		final int integer255 = 255;
		final char char9 = '9';
		final char char0 = '0';
		final char charA = 'A';
		final char charZ = 'Z';
		final char chara = 'a';
		final char charz = 'z';
		while (i < length) {
			final char character = (char) random.nextInt(integer255);
			final boolean isNumber = (character >= char0) && (character <= char9);
			final boolean isCharUpperCase = (character >= charA) && (character <= charZ);
			final boolean isCharLowerCase = (character >= chara) && (character <= charz);
			if (isNumber && numbers) {
				randomString.append(character);
				i++;
			}
			if (isCharUpperCase && upperCase) {
				randomString.append(character);
				i++;
			}
			if (isCharLowerCase && lowerCase) {
				randomString.append(character);
				i++;
			}
			if (!numbers && !upperCase && !lowerCase && (isNumber || isCharUpperCase || isCharLowerCase)) {
				randomString.append(character);
				i++;
			}
		}
		return randomString.toString();
	}

	/**
	 * Valida el formato del DNI/NIE que recibe por parámetros.
	 * 
	 * @param dni
	 *            DNI/NIE a validar
	 * @return Devuelve <code>true</code> si el DNI/NIE es válido.
	 *         <code>false</code> en caso contrario.
	 */
	public static boolean validateDNI(String dni) {
		final int uno = 1;
		final int dos = 2;
		final int veintitres = 23;
		boolean correcto = false;
		if (dni != null && (dni.startsWith("X") || dni.startsWith("Y") || dni.startsWith("Z"))) {
			String letra = dni.substring(0, uno);
			letra = letra.replace('X', '0');
			letra = letra.replace('Y', '1');
			letra = letra.replace('Z', '2');
			dni = letra + dni.substring(uno);
		}
		final Pattern pattern = Pattern.compile("(\\d{1,8})([TRWAGMYFPDXBNJZSQVHLCKEtrwagmyfpdxbnjzsqvhlcke])");
		final Matcher matcher = pattern.matcher(dni);
		if (matcher.matches()) {
			final String letra = matcher.group(dos);
			final String letras = "TRWAGMYFPDXBNJZSQVHLCKE";
			int index = Integer.parseInt(matcher.group(uno));
			index = index % veintitres;
			final String reference = letras.substring(index, index + uno);

			if (reference.equalsIgnoreCase(letra)) {
				correcto = true;
			} else {
				correcto = false;
			}
		} else {
			correcto = false;
		}
		return correcto;
	}

	/**
	 * Valida el formato del correo electrónico que recibe por parámetros.
	 * 
	 * @param email
	 *            Correo electrónico a validar
	 * @return <code>true</code> si el correo electrónico tiene un formato
	 *         válido. <code>false</code> en caso contrario.
	 */
	public static boolean validateEmail(String email) {
		boolean result = true;
		if (email == null || "".equals(email)) {
			result = false;
		} else {
			Pattern pattern = Pattern.compile("[a-zA-Z0-9]+[.[a-zA-Z0-9_-]+]*@[a-z0-9][\\w\\.-]*[a-z0-9]\\.[a-z][a-z\\.]*[a-z]$");
			Matcher matcher = pattern.matcher(email);
			if (!matcher.find()) {
				result = false;
			}
		}
		return result;
	}

	/**
	 * Devuelve un objeto <code>Map</code> con los datos del fichero de
	 * propiedades cuyo nombre recibe por parámetros. Los atributos del mapa
	 * tendrán las mismas claves que tuvieran en el fichero de propiedades de
	 * forma que, si en el fichero tenemos <code>attribute.key=value</code> para
	 * obtener el valor según los atributos del mapa debemos acceder con la
	 * clave <code>attribute.key</code>.
	 * 
	 * @param baseName
	 *            que será el nombre del fichero de propiedades
	 * @return <code>Map</code> con los datos del fichero de propiedades
	 */
	public static Map<String, String> getProperties(String baseName) {
		Map<String, String> properties = new HashMap<String, String>();

		try {
			ResourceBundle resource = ResourceBundle.getBundle(baseName);
			Enumeration<String> keys = resource.getKeys();

			while (keys.hasMoreElements()) {
				final String key = keys.nextElement();
				properties.put(key, resource.getString(key));
			}
		} catch (MissingResourceException e) {
			log.error("No se ha encontrado el fichero de propiedades \"" + baseName + ".properties\"");
		}
		return properties;
	}

	/**
	 * Elimina los espacios en blanco que pueda contener la cadena que recibe
	 * por parámetros. Si la cadena recibida es <code>null</code>, el método
	 * devolverá <code>null</code>.
	 * 
	 * @param string
	 *            Cadena para eliminar espacios
	 * @return Cadena sin espacios en blanco
	 */
	public static String deleteBlankSpaces(String string) {
		String s = null;
		if (string != null) {
			StringBuffer buffer = new StringBuffer();
			char[] array = string.toCharArray();
			for (char c : array) {
				if (c != ' ') {
					buffer.append(c);
				}
			}
			s = buffer.toString();
		}
		return s;
	}

	/**
	 * Devuelve un listado con las horas posibles del día de 0 a 23.
	 * 
	 * @return Listado de horas
	 */
	public static List<SelectItem> getHoras() {
		List<SelectItem> horas = new ArrayList<SelectItem>();
		for (int i = 0; i < 24; i++) {
			String value = Integer.valueOf(i).toString();
			if (value.length() < 2) {
				value = "0" + value;
			}
			SelectItem option = new SelectItem();
			option.setValue(value);
			option.setDescription(value);
			option.setLabel(value);
			horas.add(option);
		}
		return horas;
	}

	/**
	 * Devuelve un listado con los minutos posibles de una hora de 5 en 5.
	 * 
	 * @return Listado de minutos
	 */
	public static List<SelectItem> getMinutos() {
		List<SelectItem> minutos = new ArrayList<SelectItem>();
		for (int i = 0; i < 60; i = i + 5) {
			String value = Integer.valueOf(i).toString();
			if (value.length() < 2) {
				value = "0" + value;
			}
			SelectItem option = new SelectItem();
			option.setValue(value);
			option.setDescription(value);
			option.setLabel(value);
			minutos.add(option);
		}
		return minutos;
	}

	/**
	 * Devuelve una cadena con el formato HH:MM - HH:MM con la hora de entrada y
	 * salida.
	 * 
	 * @param hent
	 *            Hora de entrada
	 * @param ment
	 *            Minuto de entrada
	 * @param hsal
	 *            Hora de salida
	 * @param msal
	 *            Minuto de salida
	 * @return Cadena con el rango de hora de entrada - salida
	 */
	public static String cadenaRangoHora(int hent, int ment, int hsal, int msal) {
		StringBuffer entsal = new StringBuffer();
		entsal.append(hent);
		entsal.append(":");
		if (ment < 10) {
			entsal.append("0");
		}
		entsal.append(ment);
		entsal.append(" - ");
		entsal.append(hsal);
		entsal.append(":");
		if (msal < 10) {
			entsal.append("0");
		}
		entsal.append(msal);
		return entsal.toString();
	}

}
